﻿//*********************************************************
//
//    Copyright (c) Microsoft. All rights reserved.
//    This code is licensed under the Microsoft Public License.
//    THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF
//    ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY
//    IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR
//    PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.
//
//*********************************************************

namespace AstoriaOverAstoria
{
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Data.Services.Internal;
    using System.Data.Services.Providers;
    using System.Diagnostics;
    using System.Linq;
    using System.Linq.Expressions;
    using System.Reflection;

    /// <summary>Expression converter which converts projections. That is projections which mean true projections in the query, not projections
    /// which are used to express other things (like property navigation).</summary>
    internal class ProjectionConverter : ExpressionConverter
    {
        /// <summary>Constructor</summary>
        /// <param name="annotations">The resource annotations for the expressions being processed.</param>
        /// <param name="contextMapping">The context mapping, that it the metadata for the service this expression applies to.</param>
        public ProjectionConverter(ExpressionResourceAnnotations annotations, MetadataMapping contextMapping)
            : base(annotations, contextMapping)
        {
        }

        /// <summary>Visits a method call expression.</summary>
        /// <param name="m">The method call to visit.</param>
        /// <returns>The visited expression.</returns>
        internal override Expression VisitMethodCall(MethodCallExpression m)
        {
            var selectMatch = ExpressionUtil.MatchSelectCall(m);
            if (selectMatch != null)
            {
                Expression source = this.Visit(selectMatch.Source);
                Expression body = selectMatch.LambdaBody;
                LambdaExpression lambda = selectMatch.Lambda;

                if (TypeSystem.IsProjectedWrapperType(body.Type))
                {
                    body = this.ConvertProjectedWrapper(body);
                }
                else if (TypeSystem.IsExpandedWrapperType(body.Type))
                {
                    // Expansion
                    MemberInitExpression init = (MemberInitExpression)body;
                    if (init != null)
                    {
                        body = this.ConvertExpandedWrapper(init);
                    }
                }

                if (body != selectMatch.LambdaBody)
                {
                    lambda = Expression.Lambda(
                            body,
                            lambda.Parameters.ToArray());

                    return Expression.Call(
                        TypeSystem.GetLinqSelectMethod(source.Type, body.Type),
                        source,
                        lambda);
                }
            }

            return base.VisitMethodCall(m);
        }

        /// <summary>Visits a member init expression.</summary>
        /// <param name="init">The member init to visit.</param>
        /// <returns>The visited expression.</returns>
        /// <remarks>Note that this will never get called when the member init is used in Select method
        /// as we handle that in the Select. (since we need to call a different Select method then, we change the type here).</remarks>
        internal override Expression VisitMemberInit(MemberInitExpression init)
        {
            if (TypeSystem.IsProjectedWrapperType(init.Type))
            {
                return this.ConvertProjectedWrapper(init);
            }
            else if (TypeSystem.IsExpandedWrapperType(init.Type))
            {
                return this.ConvertExpandedWrapper(init);
            }

            return base.VisitMemberInit(init);
        }

        /// <summary>Visits a conditional expression.</summary>
        /// <param name="c">The conditional expression to visit.</param>
        /// <returns>The visited expression.</returns>
        internal override Expression VisitConditional(ConditionalExpression c)
        {
            if (TypeSystem.IsProjectedWrapperType(c.Type))
            {
                return this.ConvertProjectedWrapper(c);
            }

            return base.VisitConditional(c);
        }

        /// <summary>Verifies that the two list of projected properties contain the same list of properties to project.</summary>
        /// <param name="projectedPropertiesA">First list of projected properties to verify.</param>
        /// <param name="projectedPropertiesB">Second list of projected properties to verify.</param>
        private static void VerifyProjectedPropertiesForTwoTypes(List<ProjectedPropertyAssigment> projectedPropertiesA, List<ProjectedPropertyAssigment> projectedPropertiesB)
        {
            Debug.Assert(projectedPropertiesA != null, "projectedPropertiesA != null");
            Debug.Assert(projectedPropertiesB != null, "projectedPropertiesB != null");

            if (projectedPropertiesA.Count != projectedPropertiesB.Count)
            {
                throw new NotSupportedException("Projecting different list of properties based on the resource's type is not supported. This usually means usage of EPM which is not supported.");
            }

            // It's enough to verify that the set of projected property names are the same
            // it can happen that each type projects a specified property name differently, but we don't care, since the OData will hide that from us
            HashSet<string> visitedPropertyNames = new HashSet<string>(projectedPropertiesA.Select(p => p.ProjectedPropertyName));

            // Now verify that all the properties from list B are in the list A as well and that there are no additional properties in A
            foreach (var p in projectedPropertiesB)
            {
                if (!visitedPropertyNames.Contains(p.ProjectedPropertyName))
                {
                    throw new NotSupportedException("Projecting different list of properties based on the resource's type is not supported. This usually means usage of EPM which is not supported.");
                }
            }
        }

        /// <summary>Inspects the <paramref name="path"/> expression and adds null checks for each nav. property access in the path
        /// to the <paramref name="expression"/>.</summary>
        /// <param name="expression">The expression to wrap in null checks.</param>
        /// <param name="path">The path expression to use to determine property accesses.</param>
        /// <returns>New expression which has null checks around the <paramref name="expression"/>.</returns>
        private static Expression AddNavPropertyNullChecks(Expression expression, Expression path)
        {
            path = ExpressionUtil.RemoveConversionsAndTypeAs(path);

            // Now loop through the path and add a null check for each nav. property being traversed
            // Note that the path should be in form parameter.NavProperty.NavProperty.NavProperty
            while (path is MemberExpression)
            {
                // (path == null) ? null : result
                expression = Expression.Condition(
                    Expression.Equal(
                        path,
                        Expression.Constant(null, path.Type)),
                    Expression.Constant(null, expression.Type),
                    expression);

                MemberExpression member = path as MemberExpression;
                path = ExpressionUtil.RemoveConversionsAndTypeAs(member.Expression);
            }

            return expression;
        }

        /// <summary>Given member assignments for the projected wrapper this method creates a list of all projected property assignments.</summary>
        /// <param name="projectedWrapperAssignments">The assignments of projected wrapper init expression.</param>
        /// <param name="startIndex">The starting index to assign projected property indeces from.</param>
        /// <returns>Enumeration of all projected property assignments found.</returns>
        /// <remarks>This methods handles the continuation via ProjectedWrapperMany.Next property.</remarks>
        private IEnumerable<ProjectedPropertyAssigment> GetProjectedPropertyAssignments(IEnumerable<MemberAssignment> projectedWrapperAssignments, int startIndex)
        {
            foreach (MemberAssignment assignment in projectedWrapperAssignments)
            {
                if (assignment.Member.Name.StartsWith("ProjectedProperty"))
                {
                    // ProjectedProperty#
                    int projectedPropertyIndex = TypeSystem.GetProjectedPropertyIndex(assignment.Member.Name) + startIndex;
                    yield return new ProjectedPropertyAssigment()
                    {
                        MemberAssignment = assignment,
                        ProjectedPropertyIndex = projectedPropertyIndex
                    };
                }
                else if (assignment.Member.Name == "Next")
                {
                    // Next = new ProjectedWrapperMany (ProjectedWrapperManyEnd)
                    // This is a continuation - one projected wrapper can only hold up to 8 properties
                    MemberInitExpression init = ExpressionUtil.RemoveConversions(assignment.Expression) as MemberInitExpression;
                    if (init != null)
                    {
                        if (init.Type == typeof(ProjectedWrapperManyEnd))
                        {
                            // This is the end - don't go there
                        }
                        else
                        {
                            // Recursively go inside and shift the index by 8
                            foreach (var projectedPropertyAssignment in this.GetProjectedPropertyAssignments(init.Bindings.OfType<MemberAssignment>(), startIndex + 8))
                            {
                                yield return projectedPropertyAssignment;
                            }
                        }
                    }
                    else
                    {
                        // Not an init - ignore it - probably a "null"
                    }
                }
            }
        }

        /// <summary>Assuming the specified <paramref name="expr"/> is a member init expression for a ProjectedWrapper 
        /// this will return list of projected properties.</summary>
        /// <param name="expr">The expression to inspect. If this is not a member init expression the method will throw.</param>
        /// <returns>List of projected properties.</returns>
        private List<ProjectedPropertyAssigment> GetProjectedPropertyAssignments(Expression expr)
        {
            Debug.Assert(expr != null, "init != null");
            Debug.Assert(TypeSystem.IsProjectedWrapperType(expr.Type), "The init expression is not a ProjectedWrapper init.");

            MemberInitExpression init = expr as MemberInitExpression;
            if (init == null)
            {
                throw new NotSupportedException("Unexpected expression '" + expr.ToString() + "' found where MemberInitExpression was expected.");
            }

            IEnumerable<MemberAssignment> assignments = init.Bindings.OfType<MemberAssignment>();

            // We need to build the list of properties to project. The ProjectedWrapper holds a comma delimited list of
            //   property names being projected in its PropertyNameList.
            string propertyNameList = (string)((ConstantExpression)assignments.Single(a => a.Member.Name == "PropertyNameList").Expression).Value;
            string[] propertyNames = propertyNameList.Split(',');
            List<ProjectedPropertyAssigment> projectedPropertyAssignments = this.GetProjectedPropertyAssignments(assignments, 0).ToList();
            foreach (var projectedPropertyAssignment in projectedPropertyAssignments)
            {
                if (projectedPropertyAssignment.ProjectedPropertyIndex < propertyNames.Length)
                {
                    projectedPropertyAssignment.ProjectedPropertyName = propertyNames[projectedPropertyAssignment.ProjectedPropertyIndex];
                }
            }

            // Filter out those without name - it can happen that the projected wrappers contained unused slots
            projectedPropertyAssignments = projectedPropertyAssignments.Where(p => !string.IsNullOrEmpty(p.ProjectedPropertyName)).
                OrderBy(p => p.ProjectedPropertyIndex).ToList();
            Debug.Assert(projectedPropertyAssignments.Count == propertyNames.Length, "Probably two properties with the same name where projected. Should never happen.");

            return projectedPropertyAssignments;
        }

        /// <summary>Determine the resource type from the specified ProjectedWrapper <paramref name="expr"/> expression.</summary>
        /// <param name="expr">The init expression for a ProjectedWrapper to inspect.</param>
        /// <param name="knownClrType">If the caller already knows the CLR type of the projected resource it specifies it here,
        /// otherwise it can pass null and the method will determine the type from the init expression.</param>
        /// <returns>The projected type mapping.</returns>
        private ResourceType GetResourceTypeFromProjectedWrapper(Expression expr, Type knownClrType)
        {
            Debug.Assert(expr != null, "init != null");
            Debug.Assert(TypeSystem.IsProjectedWrapperType(expr.Type), "The init expression is not a ProjectedWrapper init.");

            MemberInitExpression init = expr as MemberInitExpression;
            if (init == null)
            {
                throw new NotSupportedException("Unexpected expression '" + expr.ToString() + "' found where MemberInitExpression was expected.");
            }

            Expression resourceTypeNameExpr = init.Bindings.OfType<MemberAssignment>().Single(a => a.Member.Name == "ResourceTypeName").Expression;

            // The server uses this trick of assigning "" to the ResourceTypeName when it needs to project
            //   null typed as ProjectedWrapper. Since client LINQ doesn't support conditional projection of constants (based on nullness of the entity)
            //   we ignore this. It is not needed since the client LINQ if it's projecting this as an entity will project true null
            //   if the result was null.
            var nullEqualConditionalMatch = ExpressionUtil.MatchNullEqualConditional(resourceTypeNameExpr);
            if (nullEqualConditionalMatch != null)
            {
                resourceTypeNameExpr = nullEqualConditionalMatch.IfNotNull;
            }

            string resourceTypeName = (string)((ConstantExpression)resourceTypeNameExpr).Value;

            if (knownClrType == null)
            {
                // Get the actual CLR type of the original object by examining one of the projected property vaues.
                // These are always a simple property accesses on a particular type.
                Expression propertyAccess = init.Bindings.OfType<MemberAssignment>().First(a => a.Member.Name.StartsWith("ProjectedProperty")).Expression;
                knownClrType = ((MemberExpression)ExpressionUtil.RemoveConversions(propertyAccess)).Expression.Type;
            }

            ResourceTypeMapping resourceTypeMapping;
            if (!this.MetadataMapping.TryGetResourceType(knownClrType, out resourceTypeMapping))
            {
                throw new NotSupportedException("Projection of type '" + knownClrType + "' is not supported because it's not one of the defined resource types.");
            }

            if (resourceTypeMapping.FullName != resourceTypeName)
            {
                throw new NotSupportedException("Projection of resource type '" + resourceTypeMapping.FullName + "' to a resource type named '" + resourceTypeName + "' is not supported.");
            }

            return resourceTypeMapping;
        }

        /// <summary>Returns a list of projected types and their projected properties from the specified expression.</summary>
        /// <param name="expr">The expression to inspect.</param>
        /// <returns>List of projected types and their properties sorted such that more derived type is before less derived one.</returns>
        private IEnumerable<ProjectedType> GetProjectedTypes(Expression expr)
        {
            // ProjectedWrapper can either be instantiated directly with an init expression
            //   or it's wrapped with multiple conditional expressions each testing for a different type
            MemberInitExpression init = expr as MemberInitExpression;
            if (init != null)
            {
                // The "end" of the conditionals
                ProjectedType result = new ProjectedType();

                // The last init doesn't have the wrapping conditional to tell us its type, so we have to guess it from the init expression
                result.ResourceType = this.GetResourceTypeFromProjectedWrapper(init, null);
                result.ProjectedProperties = this.GetProjectedPropertyAssignments(init);

                yield return result;
            }
            else
            {
                var typeIsConditionalMatch = ExpressionUtil.MatchTypeIsConditional(expr);
                if (typeIsConditionalMatch != null)
                {
                    // Found a type conditional - so use its type as the resource type of the "true" branch
                    ProjectedType result = new ProjectedType();
                    result.ResourceType = this.GetResourceTypeFromProjectedWrapper(typeIsConditionalMatch.IfIs, typeIsConditionalMatch.TypeOperand);
                    result.ProjectedProperties = this.GetProjectedPropertyAssignments(typeIsConditionalMatch.IfIs);

                    // Return the true branch type first as that one is more derived than the false branch otherwise the conditionals would not work correctly
                    //   since the Type Is considers inheritance as well.
                    yield return result;

                    // And now process the false branch recursively
                    foreach (var r in this.GetProjectedTypes(typeIsConditionalMatch.IfIsNot))
                    {
                        yield return r;
                    }
                }
                else
                {
                    throw new NotSupportedException("Expression '" + expr.ToString() + "' is not a recognized ProjectedWrapper expression.");
                }
            }
        }

        /// <summary>Converts a projected wrapper expression.</summary>
        /// <param name="expr">The expression which evaluates to ProjectedWrapper to process.</param>
        /// <returns>The processed expression.</returns>
        private Expression ConvertProjectedWrapper(Expression expr)
        {
            Debug.Assert(expr != null, "expr != null");
            Debug.Assert(TypeSystem.IsProjectedWrapperType(expr.Type), "expr must be a ProjectedWrapper typed for this method.");

            // ProjectedWrapper projection was used
            // We need to change it into projection into the real underlying type so that the client LINQ will materialize it
            //   as entities (tracked), so that we can determine the server type of the entity by looking at its entity descriptor
            //   as that's the only way right now to determine the type of the entity when projection into a new type is made.
            // We will only initialize the properties initialized by the ProjectedWrapper expression here, so the actuall query
            //   will also use projections to limit only to these properties.
            // After the query is processed by the client LINQ we post-process the results and turn them into ProjectedWrapper again
            //   this is necessary as the query as a whole must return the same type we were asked for (ProjectedWrapper that is).
            //   For this we use ProjectedWrapperQueryResultsProcessor.

            // First get the list of projected types
            // Note that this will also check that the expression is what we can process.
            List<ProjectedType> projectedTypes = new List<ProjectedType>(this.GetProjectedTypes(expr));

            IEnumerable<ResourceType> resourceTypes = null;
            ResourceType baseResourceType = null;
            List<ProjectedPropertyAssigment> projectedPropertyAssignments = null;

            // No need to check if there's only one type, otherwise verify that all the types project the same list of properties
            //   since we can't support different sets of projected properties based on the resource type (no support in client LINQ,
            //   in fact no support in the OData protocol itself).
            if (projectedTypes.Count > 1)
            {
                ProjectedType projectedTypeA = null;
                foreach (var projectedTypeB in projectedTypes)
                {
                    if (projectedTypeA != null)
                    {
                        VerifyProjectedPropertiesForTwoTypes(projectedTypeA.ProjectedProperties, projectedTypeB.ProjectedProperties);
                    }

                    projectedTypeA = projectedTypeB;
                }
            }

            // Use the last type as the base type and to list the properties (we already checked they all types project the same list of properties)
            ProjectedType baseProjectedType = projectedTypes[projectedTypes.Count - 1];
            baseResourceType = baseProjectedType.ResourceType;
            projectedPropertyAssignments = baseProjectedType.ProjectedProperties;
            resourceTypes = projectedTypes.Select(pt => pt.ResourceType);

            // Create the results processor
            ProjectedWrapperQueryResultsProcessor resultsProcessor = new ProjectedWrapperQueryResultsProcessor(baseResourceType.InstanceType, expr.Type, resourceTypes);

            // We need all projected properties only - the ResourceTypeName and PropertyNameList are not usefull here as we would have no place to project them into.
            MemberBinding[] bindings = new MemberBinding[projectedPropertyAssignments.Count];
            for (int i = 0; i < bindings.Length; i++)
            {
                PropertyInfo property = baseResourceType.InstanceType.GetProperty(projectedPropertyAssignments[i].ProjectedPropertyName);
                Expression propertyExpr = projectedPropertyAssignments[i].MemberAssignment.Expression;

                // The expression being assigned will probably have a Convert(object) around it since the property it's projecting into
                //   is typed as System.Object. We need to remove it so that it matches the type of the property we're projecting into.
                // In fact we might need to remove more converts as for value types these are being turned into nullables as well.
                while (propertyExpr.Type != property.PropertyType)
                {
                    if (propertyExpr.NodeType == ExpressionType.Convert)
                    {
                        propertyExpr = ((UnaryExpression)propertyExpr).Operand;
                    }
                    else
                    {
                        throw new NotSupportedException(
                            string.Format(
                                "Trying to assign expression '{0}' of type '{1}' into a property '{2}' of type '{3}'. Types don't match.",
                                projectedPropertyAssignments[i].MemberAssignment.Expression.ToString(),
                                projectedPropertyAssignments[i].MemberAssignment.Expression.Type.ToString(),
                                property.ToString(),
                                property.PropertyType.ToString()));
                    }
                }

                // Remove any "as" casts to resource types as client LINQ doesn't like those. We don't need them anyway since we can only project properties
                //   on the base type anyway.
                propertyExpr = RemoveResourceTypeCastsVisitor.Process(propertyExpr, this.Annotations);

                bindings[i] = Expression.Bind(property, propertyExpr);
                resultsProcessor.AddProjectedProperty(property.Name, projectedPropertyAssignments[i].ProjectedPropertyIndex);
            }

            // Create new init expression for the real type with only the projected properties bound
            Expression resultExpression = Expression.MemberInit(
                Expression.New(baseResourceType.InstanceType),
                bindings);

            // Register our results processor
            this.Result.AddQueryResultsProcessor(resultsProcessor);

            return resultExpression;
        }

        /// <summary>Converts an expanded wrapper init expression.</summary>
        /// <param name="init">The init expression to process.</param>
        /// <returns>The resulting expression.</returns>
        private Expression ConvertExpandedWrapper(MemberInitExpression init)
        {
            // ExpandedWrapper is projected
            // Some of the expanded properties or the expanded element itself might be ProjectedWrapper or ExpanedeWrapper projections
            //   those need to be converted (recursively). Due to this the types projected for these properties will change
            //   so we need to replace this ExpandedWrapper with a new ExpandedWrapper of the same cardinality (same number of properties)
            //   but different types (types after the conversion). Unlike the ProjectedWrapper we still keep the ExpandedWrapper in the 
            //   resulting expression, the client LINQ can handle that correctly (the ExpandedElement is treated as an entity).
            // Then we need to register query results processor which will turn the results into the original type of the ExpandedWrapper
            //   and which will apply query results processors on each of the ExpandedElement and any expanded property results (as those
            //   might be wrappers with query results processors as well).
            List<ExpandedPropertyAssignment> expandedPropertyAssignments = new List<ExpandedPropertyAssignment>(init.Bindings.Count - 2);

            // First process the ExpandedElement
            MemberAssignment expandedElementAssignment = init.Bindings.OfType<MemberAssignment>().Single(a => a.Member.Name == "ExpandedElement");

            // Convert the expanded element expression
            ExpressionConversionResult expandedElementConversionResult = ConvertInnerExpression(expandedElementAssignment.Expression);
            Expression expandedElementPath;

            // Determine the nav. property path to the expanded element entity
            if (TypeSystem.IsProjectedWrapperType(expandedElementAssignment.Expression.Type))
            {
                // It's a ProjectedWrapper, so extract the path from it
                expandedElementPath = ProjectedWrapperEntityPathExtractor.GetEntityPath(expandedElementAssignment.Expression);
            }
            else
            {
                // It's not a ProjectedWrapper which means it's the entity itself, so just use the expression as the path
                expandedElementPath = expandedElementAssignment.Expression;
            }

            // There can be only 2 cases for the ExpandedElement:
            //     a) direct property access (or the input parameter itself)
            //           - we need null checks as the path to the property may contain entity references which may be null
            //     b) ProjectedWrapper
            //           - need null checks in this case as we turn that into an entity projection which accesses properties on the possibly null
            //             expanded element entity.
            expandedElementConversionResult.SetResultExpression(AddNavPropertyNullChecks(expandedElementConversionResult.Expression, expandedElementPath));

            // Now get the description
            MemberAssignment descriptionAssignment = init.Bindings.OfType<MemberAssignment>().Single(a => a.Member.Name == "Description");
            string description = (string)((ConstantExpression)ExpressionUtil.RemoveConversions(descriptionAssignment.Expression)).Value;

            Debug.Assert(expandedElementPath != null, "Didn't find the expanded element entity path, somethings wrong.");
            Debug.Assert(description != null, "Didn't find the description, somethings wrong.");

            string[] expandedPropertyNames = description.Split(',');

            // Go through the assignments and process them
            foreach (MemberAssignment assignment in init.Bindings.OfType<MemberAssignment>())
            {
                if (assignment.Member.Name.StartsWith("ProjectedProperty"))
                {
                    // ProjectedProperty#
                    int projectedPropertyIndex = TypeSystem.GetProjectedPropertyIndex(assignment.Member.Name);
                    string expandedPropertyName = expandedPropertyNames[projectedPropertyIndex];

                    ExpressionConversionResult value = ConvertInnerExpression(assignment.Expression);

                    // There can be only these cases:
                    //     a) direct property access for an entity reference
                    //           - we need to add null checks in this case as client LINQ treats this as entity type and requires them here
                    //     b) direct property access for an entity set reference (probably wrapped in some LINQ stuff) - that is IEnumerable<T>
                    //           - add null checks since the path may contain single entity references and thus nulls
                    //     c) ExpandedWrapper 
                    //           - null's here will be determined on the ExpandedElement of such wrapper instead. Must not add null checks as client LINQ
                    //             treats this as non-entity type and doesn't support null checks on it.
                    //     d) ProjectedWrapper
                    //           - we turn this into an entity type and thus need to add null checks for the same reason as in a)
                    if (!TypeSystem.IsExpandedWrapperType(assignment.Expression.Type))
                    {
                        Expression path = expandedElementPath;

                        // If it's a entity reference add the property to the path as that can be null, for entity sets the property can't be null as it's an IEnumerable
                        //   (which can only be empty, but not null).
                        if (TypeSystem.FindIEnumerable(assignment.Expression.Type) == null)
                        {
                            path = Expression.Property(path, expandedPropertyName);
                        }

                        // We add the null check for the expandedElementPath.NavProperty, where NavProperty is the property we're expanding
                        value.SetResultExpression(AddNavPropertyNullChecks(value.Expression, path));
                    }

                    // Convert the expanded property expression
                    expandedPropertyAssignments.Add(new ExpandedPropertyAssignment()
                    {
                        ProjectedPropertyIndex = projectedPropertyIndex,
                        ConversionResult = value
                    });
                }
            }

            Debug.Assert(description != null, "The Description property was not assigned on this ExpandedWrapper.");
            Debug.Assert(expandedElementConversionResult != null, "The ExpandedElement property was not assigned on this ExpandedWrapper.");

            // Create the new ExpandedWrapper type
            // It will have the same cardinality, just different generic parameters
            Type[] genericParameters = new Type[init.Bindings.Count - 1];
            genericParameters[0] = expandedElementConversionResult.Expression.Type;
            int genericParameterIndex = 1;
            foreach (var expandedPropertyAssignment in expandedPropertyAssignments)
            {
                genericParameters[genericParameterIndex++] = expandedPropertyAssignment.ConversionResult.Expression.Type;
            }

            Type expandedWrapperType = init.Type.GetGenericTypeDefinition().MakeGenericType(genericParameters);

            // Create the query results processor
            ExpandedWrapperQueryResultsProcessor resultsProcessor =
                new ExpandedWrapperQueryResultsProcessor(expandedWrapperType, init.Type, expandedElementConversionResult.QueryResultsProcessor);

            // Construct the new bindings and register expanded properties with the results processor
            MemberBinding[] bindings = new MemberBinding[init.Bindings.Count];
            bindings[0] = Expression.Bind(
                expandedWrapperType.GetProperty("ExpandedElement"),
                expandedElementConversionResult.Expression);
            bindings[1] = Expression.Bind(
                expandedWrapperType.GetProperty("Description"),
                Expression.Constant(description));
            int bindingIndex = 2;
            foreach (var expandedPropertyAssignment in expandedPropertyAssignments)
            {
                bindings[bindingIndex++] = Expression.Bind(
                    expandedWrapperType.GetProperty(TypeSystem.GetProjectedPropertyName(expandedPropertyAssignment.ProjectedPropertyIndex)),
                    expandedPropertyAssignment.ConversionResult.Expression);
                resultsProcessor.AddExpandedProperty(
                    expandedPropertyAssignment.ProjectedPropertyIndex,
                    expandedPropertyAssignment.ConversionResult.QueryResultsProcessor);
            }

            // Create new init expression for the modified ExpandedWrapper type
            MemberInitExpression resultInit = Expression.MemberInit(
                Expression.New(expandedWrapperType),
                bindings);

            // Register our results processor
            this.Result.AddQueryResultsProcessor(resultsProcessor);

            return resultInit;
        }

        /// <summary>Helper class to store already found projected properties and their assignments.</summary>
        private class ProjectedPropertyAssigment
        {
            /// <summary>The name of the projected property (in the metadata as well as the real resource type).</summary>
            public string ProjectedPropertyName { get; set; }

            /// <summary>The projected property index, that is the index on the projected wrapper onto which this property is projected.</summary>
            public int ProjectedPropertyIndex { get; set; }

            /// <summary>The assignment expression for the property</summary>
            public MemberAssignment MemberAssignment { get; set; }
        }

        /// <summary>Helper class to store already found projected types along with their projected properties.</summary>
        private class ProjectedType
        {
            /// <summary>The resource type being projected.</summary>
            public ResourceType ResourceType { get; set; }

            /// <summary>List of properties projected for this type.</summary>
            public List<ProjectedPropertyAssigment> ProjectedProperties { get; set; }
        }

        /// <summary>Helper class to store already found expanded properties and info about them.</summary>
        private class ExpandedPropertyAssignment
        {
            /// <summary>The projected property index to which the expanded property is projected on.</summary>
            public int ProjectedPropertyIndex { get; set; }

            /// <summary>The result of converion of property value expression.</summary>
            public ExpressionConversionResult ConversionResult { get; set; }
        }
    }
}
